<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
    <head>
        <!-- Book generated using mdBook -->
        <meta charset="UTF-8">
        <title>步骤指南 - Oatpp教程</title>
        <!-- Custom HTML head -->
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="theme-color" content="#ffffff" />

        <link rel="icon" href="favicon.svg">
        <link rel="shortcut icon" href="favicon.png">
        <link rel="stylesheet" href="css/variables.css">
        <link rel="stylesheet" href="css/general.css">
        <link rel="stylesheet" href="css/chrome.css">
        <link rel="stylesheet" href="css/print.css" media="print">
        <!-- Fonts -->
        <link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
        <link rel="stylesheet" href="fonts/fonts.css">
        <!-- Highlight.js Stylesheets -->
        <link rel="stylesheet" href="highlight.css">
        <link rel="stylesheet" href="tomorrow-night.css">
        <link rel="stylesheet" href="ayu-highlight.css">

        <!-- Custom theme stylesheets -->
    </head>
    <body>
        <!-- Provide site root to javascript -->
        <script>
            var path_to_root = "";
            var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
        </script>

        <!-- Work around some values being stored in localStorage wrapped in quotes -->
        <script>
            try {
                var theme = localStorage.getItem('mdbook-theme');
                var sidebar = localStorage.getItem('mdbook-sidebar');

                if (theme.startsWith('"') && theme.endsWith('"')) {
                    localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
                }

                if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
                    localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
                }
            } catch (e) { }
        </script>

        <!-- Set the theme before any content is loaded, prevents flash -->
        <script>
            var theme;
            try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
            if (theme === null || theme === undefined) { theme = default_theme; }
            var html = document.querySelector('html');
            html.classList.remove('no-js')
            html.classList.remove('light')
            html.classList.add(theme);
            html.classList.add('js');
        </script>

        <!-- Hide / unhide sidebar before it is displayed -->
        <script>
            var html = document.querySelector('html');
            var sidebar = 'hidden';
            if (document.body.clientWidth >= 1080) {
                try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
                sidebar = sidebar || 'visible';
            }
            html.classList.remove('sidebar-visible');
            html.classList.add("sidebar-" + sidebar);
        </script>

        <nav id="sidebar" class="sidebar" aria-label="Table of contents">
            <div class="sidebar-scrollbox">
                <ol class="chapter"><li class="chapter-item expanded "><a href="DTO.html"><strong aria-hidden="true">1.</strong> DTO</a></li><li class="chapter-item expanded "><a href="Async概述.html"><strong aria-hidden="true">2.</strong> Async</a></li><li class="chapter-item expanded "><a href="Simple_vs_Async.html"><strong aria-hidden="true">3.</strong> Simple vs Async</a></li><li class="chapter-item expanded "><a href="协程.html"><strong aria-hidden="true">4.</strong> 协程</a></li><li class="chapter-item expanded "><a href="关系型对象映射框架.html"><strong aria-hidden="true">5.</strong> ORM</a></li><li class="chapter-item expanded "><a href="Monolithization.html"><strong aria-hidden="true">6.</strong> Monolithization</a></li><li class="chapter-item expanded "><a href="步骤指南.html" class="active"><strong aria-hidden="true">7.</strong> 步骤指南</a></li></ol>
            </div>
            <div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
        </nav>

        <div id="page-wrapper" class="page-wrapper">

            <div class="page">
                <div id="menu-bar-hover-placeholder"></div>
                <div id="menu-bar" class="menu-bar sticky bordered">
                    <div class="left-buttons">
                        <button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
                            <i class="fa fa-bars"></i>
                        </button>
                        <button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
                            <i class="fa fa-paint-brush"></i>
                        </button>
                        <ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
                            <li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
                            <li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
                        </ul>
                        <button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
                            <i class="fa fa-search"></i>
                        </button>
                    </div>

                    <h1 class="menu-title">Oatpp教程</h1>

                    <div class="right-buttons">
                        <a href="print.html" title="Print this book" aria-label="Print this book">
                            <i id="print-button" class="fa fa-print"></i>
                        </a>
                    </div>
                </div>

                <div id="search-wrapper" class="hidden">
                    <form id="searchbar-outer" class="searchbar-outer">
                        <input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
                    </form>
                    <div id="searchresults-outer" class="searchresults-outer hidden">
                        <div id="searchresults-header" class="searchresults-header"></div>
                        <ul id="searchresults">
                        </ul>
                    </div>
                </div>
                <!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
                <script>
                    document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
                    document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
                    Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
                        link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
                    });
                </script>

                <div id="content" class="content">
                    <main>
                        <h1 id="步骤指南"><a class="header" href="#步骤指南">步骤指南</a></h1>
<p>这份步骤指南将会帮助你使用oatpp框架进行开发。按照指南完成后你将会得到一个具有良好结构和可扩展性，并且拥有一个基本节点的web服务器</p>
<h2 id="最简单的项目"><a class="header" href="#最简单的项目">最简单的项目</a></h2>
<h3 id="api-低级别组件概览"><a class="header" href="#api-低级别组件概览">API-低级别组件概览</a></h3>
<p>先来看一个最简单的oatpp服务器应用</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

void run() {

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Print info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>采用的类：</p>
<ul>
<li>HttpRouter - 对HTTP请求进行路由。这会映射URL到Endpoint处理函数上。我们的app没有声明endpoints，因此到目前为止，任何请求都会返回 <code>404 not found</code></li>
<li>HttpConnectionHandler - 这是一个简单的HTTP连接处理函数。这会采用多线程处理由客户端发起的连接，每一个连接都会创建一个线程</li>
<li>ConnectionProvider - 提供TCP连接，并且会绑定指定的端口</li>
<li>Server - Server在一个循环中从ConnectProvider接受连接，传递给ConnectionHandler</li>
</ul>
<h3 id="添加请求处理函数"><a class="header" href="#添加请求处理函数">添加请求处理函数</a></h3>
<p>应用程序目前不会做任何事情，所有的连接都会响应404，我们需要添加HttpRequestHandler并且通过Router路由请求来添加自定义响应。</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

/** 
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
public:

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    return ResponseFactory::createResponse(Status::CODE_200, &quot;Hello World!&quot;);
  }

};

void run() {

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();
  
  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;());

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>现在如果我们在游览器中打开<code>http:/localhost:8000/hello</code> 我们将会看到 <code>Hello World!</code> 消息</p>
<pre><code class="language-text-plain">$ curl http://localhost:8000/hello
Hello World!
</code></pre>
<h3 id="使用json对象响应请求"><a class="header" href="#使用json对象响应请求">使用JSON对象响应请求</a></h3>
<p>为了序列化/反序列化对象，oatpp使用从Data-Transfer-Objects(DTO)和ObjectMappers继承的对象</p>
<p>DTO对象序列化的格式被定义在Object Mapper中</p>
<p>这个特例我们使用JSON ObjectMapper，所以我们的数据将会被序列化为JSON</p>
<pre><code class="language-text-x-c-src">#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

/**
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
  std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt; m_objectMapper;
public:

  /**
   * Constructor with object mapper.
   * @param objectMapper - object mapper used to serialize objects.
   */
  Handler(const std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;&amp; objectMapper)
    : m_objectMapper(objectMapper)
  {}

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    auto message = MessageDto::createShared();
    message-&gt;statusCode = 1024;
    message-&gt;message = &quot;Hello DTO!&quot;;
    return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
  }

};

void run() {

  /* Create json object mapper */
  auto objectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared();

  /* Create Router for HTTP requests routing */
  auto router = oatpp::web::server::HttpRouter::createShared();

  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;(objectMapper /* json object mapper */ ));

  /* Create HTTP connection handler with router */
  auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);

  /* Create TCP connection provider */
  auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<p>现在我们在游览器中打开<code>http:/localhost:8000/hello</code> 我们将会获得JSON格式的响应</p>
<pre><code class="language-text-plain">$ curl http://localhost:8000/hello
{&quot;statusCode&quot;: 1024, &quot;message&quot;: &quot;Hello DTO!&quot;}
</code></pre>
<h2 id="良好的工程结构"><a class="header" href="#良好的工程结构">良好的工程结构</a></h2>
<p>为所有的自定义节点创建请求处理函数对于小型项目而言是合适的。这里建议遵循oatpp项目结构，这样可以更容易配置和扩展代码</p>
<h3 id="工程结构"><a class="header" href="#工程结构">工程结构</a></h3>
<pre><code class="language-text-plain">|- CMakeLists.txt                        // projects CMakeLists.txt
|- src/
|    |
|    |- dto/                             // Folder containing DTOs definitions
|    |    |
|    |    |- DTOs.hpp                    // DTOs are declared here
|    |     
|    |- controller/                      // Folder containing API Controllers where all endpoints are declared
|    |    |
|    |    |- MyController.hpp            // Sample - MyController is declared here
|    |     
|    |- AppComponent.hpp                 // Application Components configuration 
|    |- App.cpp                          // main() is here
|
|- test/                                 // test folder
     |
     |- app/
     |    |
     |    |- MyApiTestClient.hpp         // Api client for test API calls is declared here
     |    |- TestComponent.hpp           // Test application components configuration
     |                                   
     |- MyControllerTest.cpp             // MyController test logic is here
     |- MyControllerTest.hpp             // MyController test header
     |- Tests.cpp                        // tests main() is here
</code></pre>
<h3 id="应用程序组件配置"><a class="header" href="#应用程序组件配置">应用程序组件配置</a></h3>
<p>使用Oatpp提供的依赖配置你的应用程序</p>
<p>通过OATPP_CREATE_COMPONENT创建环境组件，在需要的时候使用OATPP_COMPONENT注入组件</p>
<h4 id="使用注入依赖对简单的例子进行修改"><a class="header" href="#使用注入依赖对简单的例子进行修改">使用注入依赖对简单的例子进行修改</a></h4>
<p>在你工程的src文件夹下创建AppComponent.hpp文件，在这文件里对所有的应用程序组件进行初始化</p>
<p><code>AppComponent.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef AppComponent_hpp
#define AppComponent_hpp

#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;
#include &quot;oatpp/network/tcp/server/ConnectionProvider.hpp&quot;

#include &quot;oatpp/core/macro/component.hpp&quot;

/**
 *  Class which creates and holds Application components and registers components in oatpp::base::Environment
 *  Order of components initialization is from top to bottom
 */
class AppComponent {
public:

  /**
   *  Create ConnectionProvider component which listens on the port
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    return oatpp::network::tcp::server::ConnectionProvider::createShared({&quot;localhost&quot;, 8000, oatpp::network::Address::IP_4});
  }());

  /**
   *  Create Router component
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  /**
   *  Create ConnectionHandler component which uses Router component to route requests
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); // get Router component
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

  /**
   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, apiObjectMapper)([] {
    return oatpp::parser::json::mapping::ObjectMapper::createShared();
  }());

};

#endif /* AppComponent_hpp */
</code></pre>
<p>现在所有主要的组件都初始化在一个地方了，这样在替换组件的时候配置应用程序就很方便了</p>
<p><code>App.cpp</code> 可以这样重写</p>
<pre><code class="language-text-x-c-src">#include &quot;AppComponent.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;

#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

/**
 * Custom Request Handler
 */
class Handler : public oatpp::web::server::HttpRequestHandler {
private:
  /* Inject object mapper component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, m_objectMapper);
public:

  /**
   * Handle incoming request and return outgoing response.
   */
  std::shared_ptr&lt;OutgoingResponse&gt; handle(const std::shared_ptr&lt;IncomingRequest&gt;&amp; request) override {
    auto message = MessageDto::createShared();
    message-&gt;statusCode = 1024;
    message-&gt;message = &quot;Hello DTO!&quot;;
    return ResponseFactory::createResponse(Status::CODE_200, message, m_objectMapper);
  }

};

void run() {

  /* Register Components in scope of run() method */
  AppComponent components;

  /* Get router component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router);

  /* Route GET - &quot;/hello&quot; requests to Handler */
  router-&gt;route(&quot;GET&quot;, &quot;/hello&quot;, std::make_shared&lt;Handler&gt;());

  /* Get connection handler component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, connectionHandler);

  /* Get connection provider component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, connectionProvider);

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
}

int main() {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;

}
</code></pre>
<h4 id="移动dto定义到一个单独的文件中"><a class="header" href="#移动dto定义到一个单独的文件中">移动DTO定义到一个单独的文件中</a></h4>
<p>在文件夹<code>src/dto/</code> 创建文件 <code>DTOs.hpp</code></p>
<p>移动<code>MessageDto</code> 定义到<code>DTOs.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef DTOs_hpp
#define DTOs_hpp

#include &quot;oatpp/core/data/mapping/type/Object.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin DTO code-generation */
#include OATPP_CODEGEN_BEGIN(DTO)

/**
 * Message Data-Transfer-Object
 */
class MessageDto : public oatpp::DTO {

  DTO_INIT(MessageDto, DTO /* Extends */)

  DTO_FIELD(Int32, statusCode);   // Status code field
  DTO_FIELD(String, message);     // Message field

};

/* TODO - Add more DTOs here */

/* End DTO code-generation */
#include OATPP_CODEGEN_END(DTO)

#endif /* DTOs_hpp */
</code></pre>
<h4 id="使用api-controller"><a class="header" href="#使用api-controller">使用API Controller</a></h4>
<p>代替单独使用HttpRequestHandler为所有节点创建新的请求处理函数，这里推荐使用Api Controller</p>
<p>API Controller 使用代码创建模板使得添加新节点的过程变得容易，这可以帮助你在不同的API Controller中组织你的节点</p>
<h5 id="创建api-controller"><a class="header" href="#创建api-controller">创建API Controller</a></h5>
<p>在文件夹<code>src/controller/</code> 创建文件<code>MyController.hpp</code></p>
<p>添加下面的代码到<code>MyController.hpp</code> 中</p>
<pre><code class="language-text-x-c-src">#ifndef MyController_hpp
#define MyController_hpp

#include &quot;dto/DTOs.hpp&quot;

#include &quot;oatpp/web/server/api/ApiController.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;
#include &quot;oatpp/core/macro/component.hpp&quot;

#include OATPP_CODEGEN_BEGIN(ApiController) ///&lt; Begin Codegen

/**
 * Sample Api Controller.
 */
class MyController : public oatpp::web::server::api::ApiController {
public:
  /**
   * Constructor with object mapper.
   * @param objectMapper - default object mapper used to serialize/deserialize DTOs.
   */
  MyController(OATPP_COMPONENT(std::shared_ptr&lt;ObjectMapper&gt;, objectMapper))
    : oatpp::web::server::api::ApiController(objectMapper)
  {}
public:
  
  ENDPOINT(&quot;GET&quot;, &quot;/hello&quot;, root) {
    auto dto = MessageDto::createShared();
    dto-&gt;statusCode = 200;
    dto-&gt;message = &quot;Hello World!&quot;;
    return createDtoResponse(Status::CODE_200, dto);
  }
  
  // TODO Insert Your endpoints here !!!
  
};

#include OATPP_CODEGEN_END(ApiController) ///&lt; End Codegen

#endif /* MyController_hpp */
</code></pre>
<h4 id="添加控制节点到路由中"><a class="header" href="#添加控制节点到路由中">添加控制节点到路由中</a></h4>
<p>为了在Api Controller中声明的节点能够工作，我们需要添加这些节点到路由中</p>
<p>最终App.cpp看起来是这样的</p>
<pre><code class="language-text-x-c-src">#include &quot;controller/MyController.hpp&quot;
#include &quot;AppComponent.hpp&quot;

#include &quot;oatpp/network/Server.hpp&quot;

void run() {

  /* Register Components in scope of run() method */
  AppComponent components;

  /* Get router component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router);

  /* Create MyController and add all of its endpoints to router */
  auto myController = std::make_shared&lt;MyController&gt;();
  router-&gt;addController(myController);

  /* Get connection handler component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, connectionHandler);

  /* Get connection provider component */
  OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, connectionProvider);

  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */
  oatpp::network::Server server(connectionProvider, connectionHandler);

  /* Priny info about server port */
  OATPP_LOGI(&quot;MyApp&quot;, &quot;Server running on port %s&quot;, connectionProvider-&gt;getProperty(&quot;port&quot;).getData());

  /* Run server */
  server.run();
  
}

int main(int argc, const char * argv[]) {

  /* Init oatpp Environment */
  oatpp::base::Environment::init();

  /* Run App */
  run();

  /* Destroy oatpp Environment */
  oatpp::base::Environment::destroy();

  return 0;
}
</code></pre>
<h2 id="测试oat应用程序"><a class="header" href="#测试oat应用程序">测试Oat++应用程序</a></h2>
<p>测试一个Oat++程序通常需要以下几个步骤：</p>
<ol>
<li>创建应用程序测试配置文件，以便让应用程序运行在测试模式中</li>
<li>为应用程序的Api定义ApiClient</li>
<li>创建测试，让应用程序使用测试配置文件运行，让API调用API Client</li>
</ol>
<h3 id="应用程序测试组建配置"><a class="header" href="#应用程序测试组建配置">应用程序测试组建配置</a></h3>
<p>在文件夹<code>test/app/</code> 中创建文件<code>TestComponent.hpp</code> （类似于AppComponent）</p>
<pre><code class="language-text-x-c-src">#ifndef TestComponent_htpp
#define TestComponent_htpp

#include &quot;oatpp/web/server/HttpConnectionHandler.hpp&quot;

#include &quot;oatpp/network/virtual_/client/ConnectionProvider.hpp&quot;
#include &quot;oatpp/network/virtual_/server/ConnectionProvider.hpp&quot;
#include &quot;oatpp/network/virtual_/Interface.hpp&quot;

#include &quot;oatpp/parser/json/mapping/ObjectMapper.hpp&quot;

#include &quot;oatpp/core/macro/component.hpp&quot;

/**
 * Test Components config
 */
class TestComponent {
public:

  /**
   * Create oatpp virtual network interface for test networking
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, virtualInterface)([] {
    return oatpp::network::virtual_::Interface::obtainShared(&quot;virtualhost&quot;);
  }());

  /**
   * Create server ConnectionProvider of oatpp virtual connections for test
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ServerConnectionProvider&gt;, serverConnectionProvider)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, interface);
    return oatpp::network::virtual_::server::ConnectionProvider::createShared(interface);
  }());

  /**
   * Create client ConnectionProvider of oatpp virtual connections for test
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ClientConnectionProvider&gt;, clientConnectionProvider)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::virtual_::Interface&gt;, interface);
    return oatpp::network::virtual_::client::ConnectionProvider::createShared(interface);
  }());

  /**
   *  Create Router component
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, httpRouter)([] {
    return oatpp::web::server::HttpRouter::createShared();
  }());

  /**
   *  Create ConnectionHandler component which uses Router component to route requests
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::network::ConnectionHandler&gt;, serverConnectionHandler)([] {
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::web::server::HttpRouter&gt;, router); // get Router component
    return oatpp::web::server::HttpConnectionHandler::createShared(router);
  }());

  /**
   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API
   */
  OATPP_CREATE_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, apiObjectMapper)([] {
    return oatpp::parser::json::mapping::ObjectMapper::createShared();
  }());

};


#endif // TestComponent_htpp
</code></pre>
<p>注意这里使用了：</p>
<ul>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/Interface/">Network Virtual Interface</a></li>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/server/ConnectionProvider/">Server VIrtual ConnectionProvider</a></li>
<li><a href="https://oatpp.io/api/latest/oatpp/network/virtual_/client/ConnectionProvider/">Client Virtual ConnectionProvider</a></li>
</ul>
<p>oatpp虚拟网络栈能够让你运行应用程序测试在底层协议上，并不需要占用真实的主机端口</p>
<h3 id="api-test-client"><a class="header" href="#api-test-client">API Test Client</a></h3>
<p>创建测试用途的ApiClient以便测试应用程序API</p>
<p>在文件夹<code>test/app/</code>创建文件<code>MyApiTestClient.hpp</code> 使用api调用相应的应用程序API</p>
<pre><code class="language-text-x-c-src">#ifndef MyApiTestClient_hpp
#define MyApiTestClient_hpp

#include &quot;oatpp/web/client/ApiClient.hpp&quot;
#include &quot;oatpp/core/macro/codegen.hpp&quot;

/* Begin Api Client code generation */
#include OATPP_CODEGEN_BEGIN(ApiClient)

/**
 * Test API client.
 * Use this client to call application APIs.
 */
class MyApiTestClient : public oatpp::web::client::ApiClient {

  API_CLIENT_INIT(MyApiTestClient)

  API_CALL(&quot;GET&quot;, &quot;/hello&quot;, getHello)

  // TODO - add more client API calls here

};

/* End Api Client code generation */
#include OATPP_CODEGEN_END(ApiClient)

#endif // MyApiTestClient_hpp
</code></pre>
<h3 id="创建测试"><a class="header" href="#创建测试">创建测试</a></h3>
<p>在文件夹<code>test/</code> 下创建文件<code>MyControllerTest.hpp</code></p>
<pre><code class="language-text-x-c-src">#ifndef MyControllerTest_hpp
#define MyControllerTest_hpp

#include &quot;oatpp-test/UnitTest.hpp&quot;

class MyControllerTest : public oatpp::test::UnitTest {
public:

  MyControllerTest() : UnitTest(&quot;TEST[MyControllerTest]&quot; /* Test TAG for logs */){}
  void onRun() override;

};

#endif // MyControllerTest_hpp
</code></pre>
<p>在文件夹<code>test/</code> 创建文件<code>MyControllerTest.cpp</code></p>
<pre><code class="language-text-x-c-src">#include &quot;MyControllerTest.hpp&quot;

#include &quot;controller/MyController.hpp&quot;

#include &quot;app/MyApiTestClient.hpp&quot;
#include &quot;app/TestComponent.hpp&quot;

#include &quot;oatpp/web/client/HttpRequestExecutor.hpp&quot;

#include &quot;oatpp-test/web/ClientServerTestRunner.hpp&quot;

void MyControllerTest::onRun() {

  /* Register test components */
  TestComponent component;

  /* Create client-server test runner */
  oatpp::test::web::ClientServerTestRunner runner;

  /* Add MyController endpoints to the router of the test server */
  runner.addController(std::make_shared&lt;MyController&gt;());

  /* Run test */
  runner.run([this, &amp;runner] {

    /* Get client connection provider for Api Client */
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::network::ClientConnectionProvider&gt;, clientConnectionProvider);

    /* Get object mapper component */
    OATPP_COMPONENT(std::shared_ptr&lt;oatpp::data::mapping::ObjectMapper&gt;, objectMapper);

    /* Create http request executor for Api Client */
    auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider);

    /* Create Test API client */
    auto client = MyApiTestClient::createShared(requestExecutor, objectMapper);

    /* Call server API */
    /* Call hello endpoint of MyController */
    auto response = client-&gt;getHello();

    /* Assert that server responds with 200 */
    OATPP_ASSERT(response-&gt;getStatusCode() == 200);

    /* Read response body as MessageDto */
    auto message = response-&gt;readBodyToDto&lt;oatpp::Object&lt;MessageDto&gt;&gt;(objectMapper.get());

    /* Assert that received message is as expected */
    OATPP_ASSERT(message);
    OATPP_ASSERT(message-&gt;statusCode-&gt;getValue() == 200);
    OATPP_ASSERT(message-&gt;message == &quot;Hello World!&quot;);

  }, std::chrono::minutes(10) /* test timeout */);

  /* wait all server threads finished */
  std::this_thread::sleep_for(std::chrono::seconds(1));

}
</code></pre>
<p>这个测试用例使用API调用<code>GET /hello</code> 判断服务器是否相应预期的值</p>
<p>使用<code>ClientServerTestRunner</code> 运行测试服务器</p>
<h3 id="运行测试"><a class="header" href="#运行测试">运行测试</a></h3>
<p>在文件夹<code>test/</code> 创建文件<code>Tests.cpp</code></p>
<pre><code class="language-text-x-c-src">#include &quot;MyControllerTest.hpp&quot;

#include &lt;iostream&gt;

void runTests() {

  OATPP_RUN_TEST(MyControllerTest);
  
  // TODO - Add more tests here:
  // OATPP_RUN_TEST(MyAnotherTest);
  
}

int main() {

  oatpp::base::Environment::init();

  runTests();

  /* Print how much objects were created during app running, and what have left-probably leaked */
  /* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */
  std::cout &lt;&lt; &quot;\nEnvironment:\n&quot;;
  std::cout &lt;&lt; &quot;objectsCount = &quot; &lt;&lt; oatpp::base::Environment::getObjectsCount() &lt;&lt; &quot;\n&quot;;
  std::cout &lt;&lt; &quot;objectsCreated = &quot; &lt;&lt; oatpp::base::Environment::getObjectsCreated() &lt;&lt; &quot;\n\n&quot;;

  OATPP_ASSERT(oatpp::base::Environment::getObjectsCount() == 0);

  oatpp::base::Environment::destroy();

  return 0;
}
</code></pre>
<p>在所有测试结束后，oatpp环境将会检查泄漏的对象</p>
<p>如果程序发生泄漏测试将会失败</p>
<blockquote>
<p>用于测试的可执行文件不应该在构建时使用<code>-DOATPP_DISABLE_ENV_OBJECT_COUNTERS</code> 这个连接标志</p>
</blockquote>
<h2 id="完整的工程代码"><a class="header" href="#完整的工程代码">完整的工程代码</a></h2>
<p><a href="https://github.com/oatpp/oatpp-starter">在这里</a></p>

                    </main>

                    <nav class="nav-wrapper" aria-label="Page navigation">
                        <!-- Mobile navigation buttons -->
                            <a rel="prev" href="Monolithization.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                                <i class="fa fa-angle-left"></i>
                            </a>
                        <div style="clear: both"></div>
                    </nav>
                </div>
            </div>

            <nav class="nav-wide-wrapper" aria-label="Page navigation">
                    <a rel="prev" href="Monolithization.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
                        <i class="fa fa-angle-left"></i>
                    </a>
            </nav>

        </div>

        <!-- Livereload script (if served using the cli tool) -->
        <script>
            const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
            const socket = new WebSocket(wsAddress);
            socket.onmessage = function (event) {
                if (event.data === "reload") {
                    socket.close();
                    location.reload();
                }
            };

            window.onbeforeunload = function() {
                socket.close();
            }
        </script>
        <script>
            window.playground_copyable = true;
        </script>
        <script src="elasticlunr.min.js"></script>
        <script src="mark.min.js"></script>
        <script src="searcher.js"></script>
        <script src="clipboard.min.js"></script>
        <script src="highlight.js"></script>
        <script src="book.js"></script>

        <!-- Custom JS scripts -->
    </body>
</html>
